/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.gionee.navil.lockscreen.shortcut;

import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.hardware.Camera.Area;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.RelativeLayout;

import com.gionee.navil.lockscreen.R;

@SuppressLint("HandlerLeak")
public class FocusManager {
    private static final String TAG = "FocusManager";
    private static final boolean LOG = true;

    private static final int RESET_TOUCH_FOCUS = 0;
    private static final int RESET_TOUCH_FOCUS_DELAY = 3000;

    private int mState = STATE_IDLE;
    private static final int STATE_IDLE = 0; // Focus is not active.
    private static final int STATE_FOCUSING = 1; // Focus is in progress.
    // Focus is in progress and the camera should take a picture after focus finishes.
    private static final int STATE_FOCUSING_SNAP_ON_FINISH = 2;
    private static final int STATE_SUCCESS = 3; // Focus finishes and succeeds.
    private static final int STATE_FAIL = 4; // Focus finishes and fails.

    private boolean mInitialized = true;
    private boolean mFocusAreaSupported = true;

    private boolean mLockAeNeeded = true;
    private boolean mAeLock;
    private boolean mAwbLock;
    private Matrix mMatrix;

    // The parent layout that includes only the focus indicator.
    private FocusIndicatorRotateLayout mFocusIndicatorRotateLayout;
    // The focus indicator view that holds the image resource.
    private CView mFocusIndicator;
    private int mPreviewWidth; // The width of the preview frame layout.
    private int mPreviewHeight; // The height of the preview frame layout.

    private List<Area> mFocusArea; // focus area in driver format
    private List<Area> mMeteringArea; // metering area in driver format
    private String mFocusMode;

    private Handler mHandler;

    // private boolean mIsVideoFocusMode = false;
    // M: used as a flag to playsound at the first focus time

    private static boolean sNeedReset = false;

    private static final int FOCUS_FRAME_DELAY = 1000;
    Listener mListener;

    public interface Listener {
        void autoFocus();
        void cancelAutoFocus();
        public void setFocusParameters();
    }

    private class MainHandler extends Handler {
        public MainHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case RESET_TOUCH_FOCUS:
                    cancelAutoFocus();
                    break;
                default:
                    break;
            }
        }
    }

    public void setFocusAreaIndicator(View l) {
        mFocusIndicatorRotateLayout = (FocusIndicatorRotateLayout) l;
        mFocusIndicator = (CView) l.findViewById(R.id.focus_indicator);

        // Put focus indicator to the center.
        RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) mFocusIndicatorRotateLayout
                .getLayoutParams();
        int[] rules = p.getRules();
        rules[RelativeLayout.CENTER_IN_PARENT] = RelativeLayout.TRUE;
    }

    public FocusManager(View focusIndicatorRotate, Looper looper, Listener l) {
        mHandler = new MainHandler(looper);
        mMatrix = new Matrix();
        mListener = l;
        setFocusAreaIndicator(focusIndicatorRotate);

        if (sNeedReset) {
            mHandler.sendEmptyMessage(RESET_TOUCH_FOCUS);
            sNeedReset = false;
        }
    }

    public void setPreviewSize(int previewWidth, int previewHeight) {
        if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) {
            mPreviewWidth = previewWidth;
            mPreviewHeight = previewHeight;

            // Set the length of focus indicator according to preview frame size.
            int len = Math.min(mPreviewWidth, mPreviewHeight) / 4;
            android.view.ViewGroup.LayoutParams layout = mFocusIndicator.getLayoutParams();
            layout.width = len;
            layout.height = len;
        }
    }

    public void onAutoFocus(boolean focused) {
        if (LOG) {
            Log.v(TAG, "onAutoFocus focused=" + focused + " mState=" + mState + " mFocusMode=" + mFocusMode);
        }
        if (mState == STATE_FOCUSING_SNAP_ON_FINISH) {
            // Take the picture no matter focus succeeds or fails. No need
            // to play the AF sound if we're about to play the shutter
            // sound.
            if (focused) {
                mState = STATE_SUCCESS;
            } else {
                mState = STATE_FAIL;
            }
            updateFocusUI();
        } else if (mState == STATE_FOCUSING) {
            if (focused) {
                mState = STATE_SUCCESS;
            } else {
                mState = STATE_FAIL;
            }
            updateFocusUI();
            if (mFocusArea != null) {
                mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, FOCUS_FRAME_DELAY);
            }
        } else if (mState == STATE_IDLE) {
        }
    }

    public void onAutoFocusMoving(boolean moving) {
        Log.i(TAG, "onAutoFocusMoving = " + moving);
        if (mState != STATE_IDLE) {
            return;
        }

        if (moving) {
            mFocusIndicatorRotateLayout.showStart();
        } else {
            mFocusIndicatorRotateLayout.showSuccess(true);
        }
    }

    public void onSingleTapUp(int x, int y) {
        if (LOG) {
            Log.v(TAG, "onSingleTapUp x = " + x + " y = " + y);
        }
//        if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
//            return;
//        }

        // Let users be able to cancel previous touch focus.
        if ((mFocusArea != null)
                && (mState == STATE_FOCUSING || mState == STATE_SUCCESS || mState == STATE_FAIL)) {
            cancelAutoFocus();
        }

        // Initialize variables.
        int focusWidth = mFocusIndicatorRotateLayout.getWidth();
        int focusHeight = mFocusIndicatorRotateLayout.getHeight();
        Log.v(TAG, "focusWidth " + focusWidth);
        if (focusWidth == 0 || focusHeight == 0) {
            Log.i(TAG, "UI Component not initialized, cancel this touch");
            return;
        }
        int previewWidth = mPreviewWidth;
        int previewHeight = mPreviewHeight;
        if (mFocusArea == null) {
            mFocusArea = new ArrayList<Area>();
            mFocusArea.add(new Area(new Rect(), 1));
            mMeteringArea = new ArrayList<Area>();
            mMeteringArea.add(new Area(new Rect(), 1));
        }

        // Convert the coordinates to driver format.
        // AE area is bigger because exposure is sensitive and
        // easy to over- or underexposure if area is too small.
        calculateTapArea(focusWidth, focusHeight, 1f, x, y, previewWidth, previewHeight,
                mFocusArea.get(0).rect);
        calculateTapArea(focusWidth, focusHeight, 1.5f, x, y, previewWidth, previewHeight,
                mMeteringArea.get(0).rect);

        // Use margin to set the focus indicator to the touched area.
        RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) mFocusIndicatorRotateLayout
                .getLayoutParams();
        int left = Util.clamp(x - focusWidth / 2, 0, previewWidth - focusWidth);
        int top = Util.clamp(y - focusHeight / 2, 0, previewHeight - focusHeight);

        Log.v(TAG, "mFocusIndicatorRotateLayout left = " + left + " top = " + top);

        p.setMargins(left, top, 0, 0);
        // Disable "center" rule because we no longer want to put it in the center.
        int[] rules = p.getRules();
        rules[RelativeLayout.CENTER_IN_PARENT] = 0;
        mFocusIndicatorRotateLayout.requestLayout();

        Log.v(TAG, "autoFocus");
        
        // Set the focus area and metering area.
        mListener.setFocusParameters();
        
        if (mFocusAreaSupported) {
            autoFocus();
        } else { // Just show the indicator in all other cases.
            updateFocusUI();
            // Reset the metering area in 3 seconds.
            mHandler.removeMessages(RESET_TOUCH_FOCUS);
            mHandler.sendEmptyMessageDelayed(RESET_TOUCH_FOCUS, RESET_TOUCH_FOCUS_DELAY);
        }
        // M:
    }

    public void onPreviewStarted() {
        mState = STATE_IDLE;
    }

    public void onPreviewStopped() {
        mState = STATE_IDLE;
        resetTouchFocus();
        // If auto focus was in progress, it would have been canceled.
        updateFocusUI();
    }

    public void onCameraReleased() {
        onPreviewStopped();
    }

    private void autoFocus() {
        if (LOG) {
            Log.v(TAG, "Start autofocus.");
        }
        mListener.autoFocus();
        mState = STATE_FOCUSING;
        updateFocusUI();
        mHandler.removeMessages(RESET_TOUCH_FOCUS);
    }

    private void cancelAutoFocus() {
        if (LOG) {
            Log.v(TAG, "Cancel autofocus.");
        }

        // Reset the tap area before calling mListener.cancelAutofocus.
        // Otherwise, focus mode stays at auto and the tap area passed to the
        // driver is not reset.
        resetTouchFocus();
        mListener.cancelAutoFocus();
        mState = STATE_IDLE;
        updateFocusUI();
        mHandler.removeMessages(RESET_TOUCH_FOCUS);
    }

    public List<Area> getFocusAreas() {
        return mFocusArea;
    }

    public List<Area> getMeteringAreas() {
        return mMeteringArea;
    }

    public void updateFocusUI() {
//        if (!mInitialized) {
//            return;
//        }

        FocusIndicator focusIndicator = mFocusIndicatorRotateLayout;
        Log.i(TAG, "updateFocusUI, " + " mState = " + mState + " mFocusArea = " + mFocusArea
                + " focusIndicator = " + focusIndicator);
        if (mState == STATE_IDLE) {
            if (mFocusArea == null) {
                focusIndicator.clear();
            } else {
                focusIndicator.showStart();
            }
        } else if (mState == STATE_FOCUSING || mState == STATE_FOCUSING_SNAP_ON_FINISH) {
            focusIndicator.showStart();
        } else {
            if (mState == STATE_SUCCESS) {
                focusIndicator.showSuccess(false);
            } else if (mState == STATE_FAIL) {
                focusIndicator.showFail(false);
            }
        }
    }

    public void resetTouchFocus() {
        if (LOG) {
            Log.v(TAG, "resetTouchFocus mInitialized = " + mInitialized);
        }
        if (!mInitialized) {
            return;
        }

        // Put focus indicator to the center.
        RelativeLayout.LayoutParams p = (RelativeLayout.LayoutParams) mFocusIndicatorRotateLayout
                .getLayoutParams();
        int[] rules = p.getRules();
        rules[RelativeLayout.CENTER_IN_PARENT] = RelativeLayout.TRUE;
        p.setMargins(0, 0, 0, 0);
        mFocusIndicatorRotateLayout.clear();

        mFocusArea = null;
        mMeteringArea = null;
    }

    public void calculateTapArea(int focusWidth, int focusHeight, float areaMultiple, int x, int y,
            int previewWidth, int previewHeight, Rect rect) {
        int areaWidth = (int) (focusWidth * areaMultiple);
        int areaHeight = (int) (focusHeight * areaMultiple);
        int left = Util.clamp(x - areaWidth / 2, 0, previewWidth - areaWidth);
        int top = Util.clamp(y - areaHeight / 2, 0, previewHeight - areaHeight);

        RectF rectF = new RectF(left, top, left + areaWidth, top + areaHeight);
        mMatrix.mapRect(rectF);
        Util.rectFToRect(rectF, rect);
    }

    public boolean isFocusCompleted() {
        return mState == STATE_SUCCESS || mState == STATE_FAIL;
    }

    public boolean isFocusingSnapOnFinish() {
        return mState == STATE_FOCUSING_SNAP_ON_FINISH;
    }

    public void removeMessages() {
        if (mHandler.hasMessages(RESET_TOUCH_FOCUS)) {
            sNeedReset = true;
            mHandler.removeMessages(RESET_TOUCH_FOCUS);
            if (LOG) {
                Log.v(TAG, "removeMessages, we resend it next time");
            }
        }
    }

    public void setAwbLock(boolean lock) {
        mAwbLock = lock;
    }

    public void setLockAeNeeded(boolean neededLock) {
        mLockAeNeeded = neededLock;
    }

    public void setAeLock(boolean lock) {
        if (mLockAeNeeded) {
            mAeLock = lock;
        } else {
            mAeLock = false;
        }
    }

    public boolean getAwbLock() {
        return mAwbLock;
    }

    public boolean getAeLock() {
        return mAeLock;
    }

    // M:
    public boolean isCameraIdle() {
        return mState == STATE_IDLE;
    }

    public void clearFocusOnContinuous() {
        mFocusIndicatorRotateLayout.clear();
    }

    // / M: add for focus other capability
    // Note: google default setParameters() will be filled by initial parameters.
    private boolean mMeteringAreaSupported;
    private boolean mAeLockSupported;
    private boolean mAwbLockSupported;
    private boolean mContinousFocusSupported;

    public boolean getAeLockSupported() {
        return mAeLockSupported;
    }

    public boolean getAwbLockSupported() {
        return mAwbLockSupported;
    }

    public boolean getFocusAreaSupported() {
        return mFocusAreaSupported;
    }

    public boolean getMeteringAreaSupported() {
        return mMeteringAreaSupported;
    }

    public boolean getContinousFocusSupported() {
        return mContinousFocusSupported;
    }

    public FocusManager(View focusIndicatorRotate, Looper looper, String continous) {
        mHandler = new MainHandler(looper);
        mMatrix = new Matrix();

        setFocusAreaIndicator(focusIndicatorRotate);

        if (sNeedReset) {
            mHandler.sendEmptyMessage(RESET_TOUCH_FOCUS);
            sNeedReset = false;
        }

    }
}
